#include "OTAManager.h"
#include "fonts.h"

// Static instance pointer for callbacks
OTAManager* OTAManager::_instance = nullptr;

OTAManager::OTAManager(Panel& panel) 
    : _panel(panel), _otaInProgress(false), _otaProgress(0), _otaStatus(""), _otaError(""), _lastOTADraw(0) {
    _instance = this; // Set static instance for callbacks
}

bool OTAManager::begin(const char* hostname, const char* password) {
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("OTA: WiFi not connected, skipping OTA setup");
        return false;
    }
    
    Serial.println("Initializing OTA...");
    
    // Set OTA hostname and password
    ArduinoOTA.setHostname(hostname);
    ArduinoOTA.setPassword(password);
    
    // Setup callbacks
    setupCallbacks();
    
    // Begin OTA service
    ArduinoOTA.begin();
    
    Serial.println("OTA Ready");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    
    return true;
}

void OTAManager::setupCallbacks() {
    ArduinoOTA.onStart(onStartCallback);
    ArduinoOTA.onEnd(onEndCallback);
    ArduinoOTA.onProgress(onProgressCallback);
    ArduinoOTA.onError(onErrorCallback);
}

void OTAManager::handle() {
    ArduinoOTA.handle();
    updateDisplay();
}

void OTAManager::updateDisplay() {
    unsigned long currentTime = millis();
    
    // Always draw OTA status if in progress, with high frequency
    if (_otaInProgress) {
        if (currentTime - _lastOTADraw >= OTA_DRAW_INTERVAL) {
            // Aggressively clear the entire screen below OTA area to prevent clock artifacts
            _panel.fillRect(0, 12, _panel.width - 1, _panel.height - 1, 0, 0, 0);
            drawStatus();
            _lastOTADraw = currentTime;
        }
    } 
    // Show error messages immediately
    else if (_otaError != "") {
        drawStatus();
        _lastOTADraw = currentTime;
    }
    // Show status messages for a limited time
    else if (_otaStatus != "" && currentTime - _lastOTADraw < STATUS_DISPLAY_TIME) {
        if (currentTime - _lastOTADraw >= OTA_DRAW_INTERVAL) {
            drawStatus();
            _lastOTADraw = currentTime;
        }
    } 
    // Clear old status messages
    else if (_otaStatus != "" && currentTime - _lastOTADraw >= STATUS_DISPLAY_TIME) {
        _panel.fillRect(0, 0, _panel.width - 1, 11, 0, 0, 0); // Clear top area
        _otaStatus = "";
    }
}

void OTAManager::drawStatus() {
    static bool otaTextDrawn = false;
    
    // Show OTA progress if active
    if (_otaInProgress) {
        // Always draw status message during OTA
        if (_otaStatus != "") {
            drawText5x8(2, 1, _otaStatus.c_str(), 255, 255, 0);
        }
        
        // Draw progress bar (always show during OTA, even at 0%)
        uint16_t barWidth = _panel.width - 4; // Leave 2 pixels margin on each side
        uint16_t barHeight = 3; // Slightly thicker bar
        uint16_t barY = 8; // Position below status text
        uint16_t progressWidth = (barWidth * _otaProgress) / 100;
        
        // Draw progress bar background (dark gray)
        _panel.fillRect(2, barY, 2 + barWidth - 1, barY + barHeight - 1, 50, 50, 50);
        
        // Draw progress (bright green)
        if (progressWidth > 0) {
            _panel.fillRect(2, barY, 2 + progressWidth - 1, barY + barHeight - 1, 0, 255, 0);
        }
        
        // Percentage display removed per user request
        return;
    }
    
    // Reset text drawn flag when OTA is not active
    otaTextDrawn = false;
    
    // Show error messages in red
    if (_otaError != "") {
        drawText5x8(2, 1, _otaError.c_str(), 255, 0, 0);
        return;
    }
    
    // Show status messages (for non-progress states)
    if (_otaStatus != "") {
        drawText5x8(2, 1, _otaStatus.c_str(), 0, 255, 0);
    }
}

void OTAManager::clearMessages() {
    _otaStatus = "";
    _otaError = "";
    _panel.fillRect(0, 0, _panel.width - 1, 11, 0, 0, 0); // Clear top area
}

void OTAManager::drawText5x8(uint16_t x, uint16_t y, const char* text, uint8_t r, uint8_t g, uint8_t b) {
    uint16_t currentX = x;
    for (size_t i = 0; text[i] != '\0'; ++i) {
        int idx = fontIndex(text[i]);
        for (uint8_t row = 0; row < 8; ++row) {
            uint8_t bits = font5x8[idx][row];
            for (uint8_t col = 0; col < 5; ++col) {
                if (bits & (1 << (4 - col))) {
                    _panel.drawPixel(currentX + col, y + row, r, g, b);
                }
            }
        }
        currentX += 6; // 5 pixels wide + 1 pixel spacing
    }
}

// Static callback implementations
void OTAManager::onStartCallback() {
    if (!_instance) return;
    
    String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem";
    Serial.println("Start updating " + type);
    
    _instance->_otaInProgress = true;
    _instance->_otaProgress = 0;
    _instance->_otaStatus = "OTA Starting";
    _instance->_otaError = "";
    
    // Clear entire screen to remove any existing clocks
    _instance->_panel.clearScreen();
    // Immediately show OTA status to prevent blank screen
    _instance->drawStatus();
    
    Serial.println("OTA Started - Display should show progress");
}

void OTAManager::onEndCallback() {
    if (!_instance) return;
    
    Serial.println("\nOTA Update complete!");
    
    _instance->_otaInProgress = false;
    _instance->_otaStatus = "OTA Complete!";
    _instance->_otaProgress = 100;
    
    // Clear the progress area and show completion message briefly
    _instance->_panel.fillRect(0, 0, _instance->_panel.width - 1, 11, 0, 0, 0);
    _instance->drawStatus();
    
    Serial.println("OTA Ended - Display should show complete");
}

void OTAManager::onProgressCallback(unsigned int progress, unsigned int total) {
    if (!_instance) return;
    
    _instance->_otaProgress = (progress * 100) / total;
    Serial.printf("Progress: %u%% (%u/%u)\r", _instance->_otaProgress, progress, total);
    
    // Update status text only once
    static bool statusSet = false;
    if (!statusSet) {
        _instance->_otaStatus = "OTA Progress";
        statusSet = true;
    }
    
    // Only update display every 2% to reduce flicker
    static unsigned int lastDisplayedProgress = 0;
    if (_instance->_otaProgress >= lastDisplayedProgress + 2 || _instance->_otaProgress == 100) {
        // Only clear the progress bar area (y=8-11), leave text area intact (y=0-7)
        _instance->_panel.fillRect(0, 8, _instance->_panel.width - 1, 11, 0, 0, 0);
        _instance->drawStatus();
        lastDisplayedProgress = _instance->_otaProgress;
    }
}

void OTAManager::onErrorCallback(ota_error_t error) {
    if (!_instance) return;
    
    Serial.printf("Error[%u]: ", error);
    
    _instance->_otaInProgress = false;
    _instance->_otaProgress = 0;
    
    switch (error) {
        case OTA_AUTH_ERROR:
            _instance->_otaError = "OTA: Auth Failed";
            Serial.println("Auth Failed");
            break;
        case OTA_BEGIN_ERROR:
            _instance->_otaError = "OTA: Begin Failed";
            Serial.println("Begin Failed");
            break;
        case OTA_CONNECT_ERROR:
            _instance->_otaError = "OTA: Connect Failed";
            Serial.println("Connect Failed");
            break;
        case OTA_RECEIVE_ERROR:
            _instance->_otaError = "OTA: Receive Failed";
            Serial.println("Receive Failed");
            break;
        case OTA_END_ERROR:
            _instance->_otaError = "OTA: End Failed";
            Serial.println("End Failed");
            break;
        default:
            _instance->_otaError = "OTA: Unknown Error";
            Serial.println("Unknown Error");
            break;
    }
}